#include "plato_stream_impl.hh"

namespace plato {

plato::PlatoStreamImpl::PlatoStreamImpl() {
  buffer_.reset(new char[1024]);
  max_size_ = 1024;
}

plato::PlatoStreamImpl::~PlatoStreamImpl() {}

auto plato::PlatoStreamImpl::write(const char *data, std::size_t size)
    -> std::size_t {
  if (size_ + size > max_size_) {
    max_size_ += max_size_ + size;
    auto *ptr = new char[max_size_];
    memcpy(ptr, buffer_.get(), size_);
    buffer_.reset(ptr);
  }
  memcpy(buffer_.get() + write_pos_, data, size);
  size_ += size;
  write_pos_ += size;
  return size;
}

auto plato::PlatoStreamImpl::read(char *data, std::size_t size)
    -> std::size_t {
  if (size > size_) {
    size = size_;
  }
  memcpy(data, buffer_.get() + read_pos_, size);
  size_ -= size;
  read_pos_ += size;
  if (size_ == 0) {
    clear();
  }
  return size;
}

auto plato::PlatoStreamImpl::skip(std::size_t size) -> std::size_t {
  if (size > size_) {
    size = size_;
  }
  size_ -= size;
  read_pos_ += size;
  return size;
}

auto plato::PlatoStreamImpl::copy(char *data, std::size_t size)
    -> std::size_t {
  if (size > size_) {
    size = size_;
  }
  memcpy(data, buffer_.get() + read_pos_, size);
  return size;
}

auto plato::PlatoStreamImpl::operator>>(bool &b) -> PlatoStream & {
  read_type(b);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(char &c) -> PlatoStream & {
  read_type(c);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(unsigned char &c)
    -> PlatoStream & {
  read_type(c);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(std::int16_t &i)
    -> PlatoStream & {
  read_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(std::int32_t &i)
    -> PlatoStream & {
  read_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(std::int64_t &i)
    -> PlatoStream & {
  read_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(std::uint16_t &i)
    -> PlatoStream & {
  read_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(std::uint32_t &i)
    -> PlatoStream & {
  read_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(std::uint64_t &i)
    -> PlatoStream & {
  read_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(float &i) -> PlatoStream & {
  read_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator>>(double &i) -> PlatoStream & {
  read_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const bool b) -> PlatoStream & {
  write_type(b);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const char c) -> PlatoStream & {
  write_type(c);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const unsigned char c)
    -> PlatoStream & {
  write_type(c);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const std::int16_t i)
    -> PlatoStream & {
  write_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const std::int32_t i)
    -> PlatoStream & {
  write_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const std::int64_t i)
    -> PlatoStream & {
  write_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const std::uint16_t i)
    -> PlatoStream & {
  write_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const std::uint32_t i)
    -> PlatoStream & {
  write_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const std::uint64_t i)
    -> PlatoStream & {
  write_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const float i) -> PlatoStream & {
  write_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(const double i) -> PlatoStream & {
  write_type(i);
  return *this;
}

auto plato::PlatoStreamImpl::available() -> std::size_t { return size_; }

auto plato::PlatoStreamImpl::clear() -> void {
  read_pos_ = 0;
  write_pos_ = 0;
  size_ = 0;
}

auto plato::PlatoStreamImpl::data() -> const char * { return buffer_.get(); }

auto plato::PlatoStreamImpl::operator>>(PlatoStream &stream)
    -> PlatoStream & {
  if (size_) {
    stream.write(buffer_.get() + read_pos_, size_);
  }
  clear();
  return *this;
}

auto plato::PlatoStreamImpl::operator<<(PlatoStream &stream)
    -> PlatoStream & {
  if (stream.available()) {
    write(stream.data(), stream.available());
  }
  stream.clear();
  return *this;
}

} // namespace plato
